package com.salt.core.verify.image.utils;

import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FilenameUtils;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;

import static com.salt.core.verify.image.constant.Base64Constant.BASE64_CAPTURE;
import static com.salt.core.verify.image.constant.Base64Constant.BASE64_FORMAT_PNG;

/**
 * @Description
 * @Author salt
 * @Date 2022/9/20
 * @Version 1.0.1
 */
public class ImageUtil {

    /**
     * 切割图片
     * @param templateInputStream 模版文件输入流
     * @param targetInputStream 目标文件输入流
     * @param targetFileName 目标文件名
     * @param oriWidth 原文件宽度
     * @param oriHeight 原文件高度
     * @param bold 画笔宽度为几个像素
     * @return
     */
    @SneakyThrows
    public static Map<String, Object> cutPicture(InputStream templateInputStream,
                                                 InputStream targetInputStream,
                                                 InputStream targetInputStream1,
                                                 String targetFileName,
                                                 Integer oriWidth, Integer oriHeight,
                                                 Integer bold) {
        return cutPicture(ImageIO.read(templateInputStream),
                ImageIO.read(targetInputStream),
                targetInputStream1,
                getExtension(targetFileName), oriWidth, oriHeight, bold);
    }

    /**
     * 切割图片
     * @param templateInputStream 模版文件输入流
     * @param targetInputStream 目标文件输入流
     * @param oriWidth 原文件宽度
     * @param oriHeight 原文件高度
     * @param bold 画笔宽度为几个像素
     * @return
     */
    @SneakyThrows
    public static Map<String, Object> cutPicture(InputStream templateInputStream,
                                                 InputStream targetInputStream,
                                                 InputStream targetInputStream1,
                                                 InputStream targetInputStream2,
                                                 Integer oriWidth, Integer oriHeight,
                                                 Integer bold) {
        return cutPicture(ImageIO.read(templateInputStream),
                ImageIO.read(targetInputStream),
                targetInputStream1,
                getFileType(targetInputStream2), oriWidth, oriHeight, bold);
    }

    /**
     * 切割图片
     * @param templateFile 模版文件
     * @param targetFile 目标文件
     * @param oriWidth 原文件宽度
     * @param oriHeight 原文件高度
     * @param bold 画笔宽度为几个像素
     * @return
     */
    @SneakyThrows
    public static Map<String, Object> cutPicture(File templateFile, File targetFile,
                                                 Integer oriWidth, Integer oriHeight,
                                                 Integer bold) {
        if (templateFile.exists() && targetFile.exists())
            return cutPicture(ImageIO.read(templateFile),
                    ImageIO.read(targetFile),
                    new FileInputStream(targetFile),
                    getFileExtension(targetFile), oriWidth, oriHeight, bold);
        throw new RuntimeException("TemplateFile or TargetFile is empty");
        /*int oriWidth = 590;
        int oriHeight = 360;*/
        //int oriWidth = 480;
        //int oriHeight = 240;
        //int bold = 5; // 画笔宽度为5个像素
    }

    /**
     *
     * @param templateImage
     * @param oriImage
     * @param oriInputStream
     * @param oriImageType
     * @param oriWidth
     * @param oriHeight
     * @param bold
     * @return
     */
    public static Map<String, Object> cutPicture(BufferedImage templateImage,
                                                 BufferedImage oriImage,
                                                 InputStream oriInputStream,
                                                 String oriImageType,
                                                 Integer oriWidth, Integer oriHeight,
                                                 Integer bold) {
        int templateWidth = templateImage.getWidth();
        int templateHeight = templateImage.getHeight();
        // 获取切割出的图片（阴影层图片）
        // 根据模版图以及目标图的宽、高获取目标图片上的切割的起始坐标
        Map<String, Integer> coordinates = startCoordinates(oriWidth, oriHeight, templateWidth, templateHeight);
        // 根据起始坐标、目标图输入流、目标图片的类型获取切割区域
        BufferedImage targetImageNoCut = targetArea(coordinates.get("x"), coordinates.get("y"), templateWidth, templateHeight, oriInputStream, oriImageType);
        // 生成新的空白图片
        BufferedImage superImage = new BufferedImage(templateWidth, templateHeight, templateImage.getType());
        Graphics2D graphics = superImage.createGraphics();
        graphics.setBackground(Color.white);
        // 根据模版和切割区域在新的空白图片绘图
        superImage = cutPictureByTemplate(targetImageNoCut, templateImage, superImage);
        // 设置“抗锯齿”的属性
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphics.setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
        graphics.drawImage(superImage, 0, 0, null);
        graphics.dispose();
        // 原始图片添加遮罩层（底图）
        oriImage = cutOriPictureByTemplate(oriImage, templateImage, coordinates.get("x"), coordinates.get("y"));
        Map<String, Object> map = new HashMap<>();
        map.put("superImage", BASE64_CAPTURE + imageToBase64(superImage, BASE64_FORMAT_PNG));
        map.put("baseImage", BASE64_CAPTURE + imageToBase64(oriImage, BASE64_FORMAT_PNG));
        map.put("x", coordinates.get("x"));
        map.put("captureId", uuid());
        return map;
    }

    /**
     * 获取文件后缀名
     * @param file 文件
     * @return 后缀名
     */
    public static String getFileExtension(File file) {
        return getExtension(file.getName());
    }

    /**
     * 获取文件后缀名
     * @param fileName 文件名
     * @return 后缀名
     */
    public static String getExtension(String fileName) {
        if (fileName.lastIndexOf(".") != -1 && fileName.lastIndexOf(".") != 0)
            return fileName.substring(fileName.lastIndexOf(".") + 1);
        return null;
    }

    /**
     * 生成图片验证码的唯一ID
     */
    public static String uuid() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    /**
     * 根据模版给原始图片添加遮罩层
     * @param oriImage 原始图片
     * @param templateImage 模版图片
     * @param x 起始坐标X
     * @param y 起始坐标Y
     * @return 添加遮罩层后的原始图片
     */
    public static BufferedImage cutOriPictureByTemplate(BufferedImage oriImage, 
                                                        BufferedImage templateImage, int x, int y) {
        // 源文件备份图像矩阵 支持alpha通道的rgb图像
        BufferedImage oriCopyImage = new BufferedImage(oriImage.getWidth(), oriImage.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);

        int[][] oriImageArray = rgbImageArray(oriImage);  // 原始图像矩阵
        int[][] templateImageArray = rgbImageArray(templateImage);  // 模版图像矩阵

        //copy 源图做不透明处理
        for (int i = 0; i < oriImageArray.length; i++) {
            for (int j = 0; j < oriImageArray[0].length; j++) {
                int rgb = oriImage.getRGB(i, j);
                int r = (0xff & rgb);
                int g = (0xff & (rgb >> 8));
                int b = (0xff & (rgb >> 16));
                //无透明处理
                rgb = r + (g << 8) + (b << 16) + (255 << 24);
                oriCopyImage.setRGB(i, j, rgb);
            }
        }

        for (int i = 0; i < templateImageArray.length; i++) {
            for (int j = 0; j < templateImageArray[0].length; j++) {
                int rgb = templateImage.getRGB(i, j);
                //对源文件备份图像(x+i,y+j)坐标点进行透明处理

                if (rgb != 16777215 && rgb < 0) {
                    int rgb_ori = oriCopyImage.getRGB(x + i, y + j);
                    int r = (0xff & rgb_ori);
                    int g = (0xff & (rgb_ori >> 8));
                    int b = (0xff & (rgb_ori >> 16));
                    rgb_ori = r + (g << 8) + (b << 16) + (130 << 24);
                    oriCopyImage.setRGB(x + i, y + j, rgb_ori);
                } else {
                    //do nothing
                }
            }
        }

        return oriCopyImage;
    }

    /**
     * 获取目标区域
     * @param x 起点坐标X
     * @param y 起点坐标Y
     * @param templateWidth
     * @param templateHeight
     * @param ois 源文件输入流
     * @param fileType 文件类型（png,jpg等）
     * @return
     */
    @SneakyThrows
    public static BufferedImage targetArea(int x, int y, int templateWidth, int templateHeight, InputStream ois,
                                           String fileType) {
        Iterator<ImageReader> imageReaderIterator = ImageIO.getImageReadersByFormatName(fileType);
        ImageReader imageReader = imageReaderIterator.next(); // 用ImageReader解析和解码图像
        ImageInputStream inputStream = ImageIO.createImageInputStream(ois); // 获取图片输入流

        /*byte[] bs = new byte[1];
        inputStream.read(bs);
        String type = Integer.toHexString(bs[0]&0xFF);*/

        imageReader.setInput(inputStream, true); // 按顺序读取输入源中的图像

        ImageReadParam imageReadParam = imageReader.getDefaultReadParam(); //
        Rectangle rectangle = new Rectangle(x, y, templateWidth, templateHeight); // 指定坐标空间中的一个区域
        imageReadParam.setSourceRegion(rectangle);
        // 使用所提供的 ImageReadParam 读取，通过索引 imageIndex 指定的对象
        BufferedImage bufferedImage = imageReader.read(0, imageReadParam);
        return bufferedImage;
    }

    /**
     * 获取切图在原图中开始的点的坐标
     * @param oriWidth 原始图宽度
     * @param oriHeight 原始图高度
     * @param templateWidth 模版图宽度
     * @param templateHeight 模版图高度
     * @return 切图在原图中开始的X轴Y轴的坐标
     */
    private static Map<String, Integer> startCoordinates(Integer oriWidth,
                                                              Integer oriHeight,
                                                              Integer templateWidth,
                                                              Integer templateHeight) {
        int x = 0, y = 0;
        Random random = new Random();
        if (oriWidth - templateWidth > 0)
            x = random.nextInt(oriWidth - 3*templateWidth) + 2*templateWidth + 0;

        if (oriHeight - templateHeight > 0)
            y = random.nextInt(oriHeight - templateHeight) + templateHeight + 0;

        Map<String, Integer> coordinates = new HashMap<>();
        coordinates.put("x", x);
        coordinates.put("y", y);
        return coordinates;
    }

    /**
     * 根据模版抠图
     * 颜色可以通过数字方式记录，以24位图像为例，它可以最多记录16777216种不同的颜色，分别记录为0-16777215的数字
     * RGB是一个函数，它最终会返回合成色对应的整数值（合成色对应的整数值 = 红色值+绿色值*256+蓝色值*65536），
     * RGB(0,0,0)的值为0（黑色），RGB(255,255,255)的值为16777215（白色），RGB(255,0,0)的值为255（红色），
     * RGB(0,255,0)的值为65280，RGB(0,0,255)的值为16711680（蓝色）
     * @param oriImage 原始图片
     * @param templateImage 模版图片
     * @param targetImage 原始图片
     * @return 扣了图片之后的原始图片
     */
    public static BufferedImage cutPictureByTemplate(BufferedImage oriImage, BufferedImage templateImage,
                                                   BufferedImage targetImage) {

        int[][] oriImageArray = rgbImageArray(oriImage);  // 原始图像矩阵
        int[][] templateImageArray = rgbImageArray(templateImage);  // 模版图像矩阵

        for (int i = 0; i < templateImageArray.length; i++) {  // 模版图片的宽度
            for (int j = 0; j < templateImageArray[0].length; j++) {

                int rgb = templateImageArray[i][j]; // 获取合成色对应的整数值
                // 如果模板图像当前像素点不是白色 copy源文件信息到目标图片中
                /*if (rgb != 16777215 && rgb <= 0) {
                    int orirgb = oriImageArray[i][j];
                    targetImage.setRGB(i, j, orirgb);
                }*/
                /*if ( i < oriImageArray.length && j < oriImageArray[0].length) {
                    if (rgb != 16777215 && rgb < 0) {
                        targetImage.setRGB(i, j, oriImageArray[i][j]);
                    }
                }*/

                if (rgb != 16777215 && rgb < 0) {
                    targetImage.setRGB(i, j, oriImageArray[i][j]);
                }

            }
        }

        return targetImage;

    }

    /**
     * 图片流转base64编码
     * @param bufferedImage 图片流
     * @param formatName 图片格式（png,jpg等）
     * @return 图片流的base64编码
     */
    @SneakyThrows
    public static String imageToBase64(BufferedImage bufferedImage, String formatName) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(); //Byte数组输出流
        //ImageIO.write(bufferedImage, "png", baos);
        // 利用ImageIO类提供的write方法，将bi以png图片的数据模式写入流
        ImageIO.write(bufferedImage, formatName, baos); //
        byte[] byteArray = baos.toByteArray(); // 将Byte数组输出流转为数组
        // 生成base64编码
        String base64Image = Base64.encodeBase64String(byteArray);
        return base64Image;
    }

    /**
     * 通过RGB获得图像像素矩阵
     * 次方式为沿着Height方向扫描
     * 以图片左上角为原点，X轴正方向沿着Width方向，Y轴正方向沿着Height方向
     * @param bufferedImage 图片流
     * @return 像素矩阵
     */
    public static int[][] rgbImageArray(BufferedImage bufferedImage) {
        int width = bufferedImage.getWidth();
        int height = bufferedImage.getHeight();
        int[][] data = new int[width][height];
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                data[i][j] = bufferedImage.getRGB(i, j);
            }
        }
        return data;
    }

    @SneakyThrows
    public static String getFileType(InputStream inputStream) {
        byte[] bs = new byte[1];
        inputStream.read(bs);
        String type = Integer.toHexString(bs[0]&0xFF);
        if("ff".equalsIgnoreCase(type))
            return  "JPEG";
        if("d8".equalsIgnoreCase(type))
            return "JPG";
        if("89".equalsIgnoreCase(type))
            return "PNG";
        return null;
    }

    public static String getFileType(String filePath) {
        FileInputStream fis = null;
        /**
         * 根据文件名称，获取后缀名的方式，但是不保险
         */
        String extension = FilenameUtils.getExtension(filePath);
        try {
            fis = new FileInputStream(new File(filePath));
            byte[] bs = new byte[1];
            fis.read(bs);
            String type = Integer.toHexString(bs[0]&0xFF);
            if("ff".equalsIgnoreCase(type))  extension = "JPEG";
            if("d8".equalsIgnoreCase(type))  extension = "JPG";
            if("89".equalsIgnoreCase(type)) extension = "PNG";
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("获取图片类型出错 : " +  filePath);
        } finally {
            try{
                if(fis != null) fis.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return extension;
    }




}
